/* Copyright (C) 2021 Rain */

/* This file is part of CFS. */

/* 
  CFS is free software: you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation, either version 3 of the License, or 
  (at your option) and later version. 
*/

/*
  CFS is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  GNU General Public License for more details. 
*/

/*
  You should have received a copy of the GNU General Public License 
   along with CFS. If not, see <https://www.gnu.org/licenses/>.  
*/




#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include <string.h>
#include <fcntl.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/time.h>

#define __CFS
#include "cfs.h"
#include "bitmap.h"


void fill_super(int);
void setup_bbm(int);
void setup_bim(int);
void setup_ibm(int);
void setup_root(int);

void setup_rfile(int, char *, struct dentry *, __uint8_t);
__uint32_t gettime(void);


int main(int argc, const char **argv) {
	char *fname = (char *) argv[1];
	int fd;

	if (!fname) {
		printf("fatal: excepted one argument, but %d given. \n", argc - 1);
		printf("usage: %s device\n", argv[0]);

		return 1;
	}

	fd = open(fname, O_RDWR | O_CREAT);

	if (fd < 0) {
		printf("fatal: failed when try to open %s: %s\n", fname, strerror(errno));

		return 1;
	}

	printf("fill super block...");
	fill_super(fd);
	printf("OK\n");

	printf("setup block bitmap...");
	setup_bbm(fd);
	printf("OK\n");

	printf("setup bim");
	setup_bim(fd);
	printf("OK\n");

	printf("setup inodes bitmap");
	setup_ibm(fd);
	printf("OK\n");

	printf("setup root...");
	setup_root(fd);
	printf("OK\n");

	close(fd);

	printf("all done\n");

	return 0;
}


void fill_super(int fd) {
	/* fill sb, and write it back to the fd */
	memset(&sb, 0x00, sizeof(struct superblock));

	sb.magic = MAGIC;
	sb.nblocks = lseek(fd, 0, SEEK_END) >> BLOCK_SHIFT;

	/* inodes size : nblocks = 1 : 16 */
	sb.nb_inodes = sb.nblocks >> 4;
	sb.nb_ibm = ALIGNUP_BLOCK(sb.nb_inodes * sizeof(struct inode) >> 3) >> BLOCK_SHIFT;

	sb.ninodes = (sb.nb_inodes << BLOCK_SHIFT) / sizeof(struct  inode);
	
	lseek(fd, 0, SEEK_SET);

	/* (sb.nblocks >> 3) + 1 is the size (bytes) of bbm, and align it to blocks */
	sb.nb_bbm = ALIGNUP_BLOCK((sb.nblocks >> 3) + 1) >> BLOCK_SHIFT;

	/* the second block */
	sb.bbm_bn = 1;

	/* << 3: * 8 (size of a bim_entry) */
	sb.nb_bim = ALIGNUP_BLOCK(sb.nblocks << 3) >> BLOCK_SHIFT;
	sb.bim_bn = 1 + sb.nb_bbm;

	sb.ibm_bn = sb.bim_bn + sb.nb_bim;
	sb.inodes_bn = sb.ibm_bn + sb.nb_ibm;

	/* root_ino will set in setup_root */

	/* and write back */

#define update_sb() 						\
	lseek(fd, 512, SEEK_SET);				\
	write(fd, (char *) &sb, sizeof(struct superblock));	\
	lseek(fd, 0, SEEK_SET);


	/* ignore that MBR (512 bytes) */
	update_sb();

	return;
}


/* clean bitmap, and then write (only first block) */
void setup_bbm(int fd) {
	/* clear all bits in bitmap */

	int bn;
	char block[BLOCK_SIZE] = {0};

	lseek(fd, sb.bbm_bn << BLOCK_SHIFT, SEEK_SET);

	for (bn = sb.bbm_bn; bn < sb.bbm_bn + sb.nb_bbm; bn++) {
		/* write one block to fd */
		write(fd, block, BLOCK_SIZE);

		/* point to next point */
		lseek(fd, BLOCK_SIZE, SEEK_CUR);
	}

	/* and calc how many blocks are using */

	__nr_t blocks_used = sb.bbm_bn + sb.nb_bbm + sb.nb_bim + sb.nb_ibm + sb.nb_inodes;

	sb.first_data_block = blocks_used;

	update_sb();

	lseek(fd, sb.bbm_bn << BLOCK_SHIFT, SEEK_SET);

	/* set them */

	unsigned char *bm = malloc(ALIGNUP_BLOCK(blocks_used >> 3));

	if (!bm) {
		printf("fatal: failed to allocate space for bitmap. %d bytes needs. \n", 
			blocks_used >> 3);

		exit(1);
	}

	memset(bm, 0x00, ALIGNUP_BLOCK(blocks_used >> 3));

	for (bn = 0; bn < blocks_used; bn++) 
		bitmap_set(bm, bn, 1);

	/* write it back */

	for (bn = 0; bn < ALIGNUP_BLOCK(blocks_used >> 3) >> BLOCK_SHIFT; bn++) 
		write(fd, bm + (bn << BLOCK_SHIFT), BLOCK_SIZE);

	free(bm);

	return;
}

void setup_ibm(int fd) {
	int i;
	char block[BLOCK_SIZE] = {0};

	/* put 16 '.'s on the screen */
	int points = sb.nb_ibm >> 4;

	lseek(fd, sb.ibm_bn << BLOCK_SHIFT, SEEK_SET);

	for (i = 0; i < sb.nb_ibm; i++) {
		/* write one block to fd */
		write(fd, block, BLOCK_SIZE);

		if (points && i % points == 0) 
			putc('.', stdout);
	}

	if (!points) 
		printf("................");

	return;
}


void setup_bim(int fd) {
	int i;
	char block[BLOCK_SIZE] = {0};

	/* put 16 '.'s on the screen */
	int points = sb.nb_bim >> 4;

	lseek(fd, sb.bim_bn << BLOCK_SHIFT, SEEK_SET);

	for (i = 0; i < sb.nb_bim; i++) {
		/* write one block to fd */
		write(fd, block, BLOCK_SIZE);

		if (i % points == 0) 
			putc('.', stdout);
	}

	return;
}


/* 
 * setup that root directory '/'. 
 *
 * STEPS: 
 * 	1. setup root_ino in sb. 
 *	2. setup bits in bim (initialize size 4k)
 *	3. setup dentry
 * */
void setup_root(int fd) {
	sb.root_ino.inode = ROOT_INO;
	sb.root_ino.pinode = ROOT_INO;

	sb.root_ino.access = A_DIR | A_USERRD | A_ROOT_RW | A_EXEC;

	/* 4096 bytes = 1 block */
	sb.root_ino.blocks_nr = 1;
	sb.root_ino.upsize = 0;

	sb.root_ino.ctime = sb.root_ino.wtime = gettime();

	/* '.' + '..' */
	sb.root_ino.nlinks = 2;

	/* unused now in user space */
	sb.root_ino.uid = sb.root_ino.gid = 0;
	sb.root_ino.eaccess = 0;

	update_sb();

	/* okay, now setup bim */
	
	struct bim_entry entry;

	memset(&entry, 0x00, sizeof(struct bim_entry));
	
	entry.inode = ROOT_INO;
	entry.th_block = 0;

	/* write back */
	lseek(fd, sb.bim_bn << BLOCK_SHIFT, SEEK_SET);
	write(fd, &entry, sizeof(struct bim_entry));

	/* now root's 1st block is the first_data_block */
	/* setup '.' and '..' in it */

	struct dentry entries[BLOCK_SIZE / sizeof(struct dentry)];
	memset(entries, 0x00, BLOCK_SIZE);

	/* for clean these */
	lseek(fd, sb.first_data_block << BLOCK_SHIFT, SEEK_SET);
	write(fd, (void *) entries, BLOCK_SIZE);

	sb.next_inode = ROOT_INO + 1;

	setup_rfile(fd, ".", entries, A_DIR | A_LINK | A_USERRD | A_ROOT_RW);
	setup_rfile(fd, "..", entries, A_DIR | A_LINK | A_USERRD | A_ROOT_RW);

	update_sb();

	/* write back to device */
	lseek(fd, sb.first_data_block << BLOCK_SHIFT, SEEK_SET);
	write(fd, entries, BLOCK_SIZE);

	return;
}



static __inode_t find_free_inode(int fd) {
	int i = 0;
	__uint8_t block[BLOCK_SIZE];

repeat:
	lseek(fd, (sb.ibm_bn + i) << BLOCK_SHIFT, SEEK_SET);
	read(fd, block, BLOCK_SIZE);

	int j;

	for (j = 0; bitmap_read(block, j) && j < 4096; j++);

	if (j == 4096) {
		i++;
		goto repeat;
	}

	bitmap_set(block, j, 1);
	lseek(fd, (sb.ibm_bn + i) << BLOCK_SHIFT, SEEK_SET);
	write(fd, block, BLOCK_SIZE);

	return (i << BLOCK_SHIFT << 3) + j;
}


void setup_rfile(int fd, char *fname, struct dentry *dentry, __uint8_t acc) {
	__inode_t ino = find_free_inode(fd);

	struct inode inode;

	inode.inode = sb.next_inode++;

	inode.pinode = ROOT_INO;
	inode.access = acc;
	inode.blocks_nr = 0;
	inode.upsize = 0;

	inode.ctime = inode.wtime = gettime();
	inode.nlinks = 1;

	inode.uid = inode.gid = 0;
	inode.eaccess = 0;

	lseek(fd, (sb.inodes_bn << BLOCK_SHIFT) + inode.inode * sizeof(struct inode), SEEK_SET);
	write(fd, &inode, sizeof(struct inode));

	dentry[ino].ino = inode.inode;
	strncpy(dentry[ino].name, fname, FNAME_LEN - 1);
	/* and sync dentry */

	/* we only sync the first block, because this is mkfs. :-) */
	lseek(fd, sb.first_data_block << BLOCK_SHIFT, SEEK_SET);
	write(fd, dentry, BLOCK_SIZE);

	return;

#undef inode
}


__uint32_t gettime(void) {
	struct timeval t = {0};

	gettimeofday(&t, NULL);
	return t.tv_sec;
}

